package decisionLines.model;

import java.util.ArrayList;

/**
 * Class for the Decision Lines Event
 */
public class DecisionLinesEvent {
	final int numRounds; // m
	final int numChoices; // n
	ArrayList<Line> lines = new ArrayList<Line>();
	
	/**
	 * Constructor: The Decision Lines Event class
	 * @param n - Int, the number of choices for the event
	 * @param m - Int, the number of rounds for the event
	 */
	public DecisionLinesEvent(int n, int m){
		// Question: should the constructor constrain n and M to be between 3 and 8,
		// or is that only handled by user input? 
		this.numChoices = n;
		this.numRounds = m;
		
		// Populate the arraylist of lines associated with this Decision Lines Event.
		// The arraylist is zero-based, so loop is constrained to i < n.
		for (int i = 0; i < n; i++){
			this.lines.add(new Line(i));
		}
	}
	
	/**
	 * Method: Get the number of choices for the event.
	 * @return numChoices: Int, The number of choices for the event.
	 */
	public int getNumChoices(){
		return numChoices;
	}
	
	
	/**
	 * Method: Get the number of rounds for the event.
	 * @return numRounds: Int, The number of rounds for the event.
	 */
	public int getNumRounds(){
		return numRounds;
	}
	
	/**
	 * Method: Returns the array list of lines.
	 * @return lines: ArrayList, The lines associated with the event.
	 */
	public ArrayList<Line> getLines(){
		return lines;
	}
	
	/**
	 * Method: Set the array of choices
	 * @return String[]: String array, The choices in order that they were selected
	 */
	public String[] prioritizedChoices(){
		String[] choices = new String[lines.size()];
		int height;
		int currentPosition;
		Line currentLine;
		Edge currentEdge;
		
		//Iterate through each line in the decision line event to determine the final position of each
		for(int i=0; i<lines.size(); i++){
			// initialize these variables at the beginning of each loop to represent the top of each line (choice) 
			height = 0;
			currentPosition = i;
			currentLine = lines.get(i);
			currentEdge = currentLine.nextEdge(height);
			
			/*
			 * Traverse each path from the top of the line through all edges to the final position.
			 * 
			 * NOTE: need to check for null case in while loop.
			 */
			while(currentEdge != null){
				/*
				 *  If the current position is the left side of the current edge, 
				 *  need to move the position to the right and vice versa.
				 */
				if(currentPosition == currentEdge.getLeft()){
					currentPosition = currentEdge.getRight();
				} else if(currentPosition == currentEdge.getRight()){
					currentPosition = currentEdge.getLeft();
				}
				height = currentEdge.getHeight();
				currentLine = lines.get(currentPosition);
				currentEdge = currentLine.nextEdge(height);
			}

			// add the choice for the line on which we started to the proper final position in the return array
			choices[currentPosition] = lines.get(i).getChoice();
		}
		
		return choices;
	}
	
	/**
	 * Method: Check that the edge placed is valid; use prior to actually adding the edge. 
	 * @param newEdge - Edge, the edge being checked for validity
	 * @return boolean: True if line is valid, false otherwise
	 */
	public boolean isValidEdge(Edge newEdge){
		/* 
		 * Edge validity rules:
		 *   1. Edge must be be placed on lines that actually exist - i.e., associated only with lines 0 to n-1
		 *   2. Edge must be at least 7 pixels vertically from any other edges (to its left or right).
		 *   3. Left line of edge must actually be to the left of (i.e., smaller line number) the right line of the edge
		 *   4. Edges must be between neighboring lines (i.e., |left - right| = 1)
		 *   
		 * NOTE: For consistency checking, the right of the line is defined by the right most point as visualized
		 *       on the GUI, and not the second click of placing a line.
		 */
		boolean isValid = false; // track validity
		int h; // for checking edge heights
		int minEdgeSpacing = 7; // minimum allowable edge spacing
		
		/* 
		 * RULE 1: Check that the left of the edge is at line 0 or above and 
		 * that the right is no more than the amount of lines displayed.
		 * Note that this rule needs to be checked first to prevent issues with
		 * trying to reference non-existent lines in the rules below.
		 */
		if((newEdge.getLeft() >= 0) && (newEdge.getRight() < lines.size())){
			isValid = true;
		} else {
			return false; // if the edge is invalid, skip the rest of the rule checking
		}
		
		
		/* 
		 * RULE 2: Check that the height of the edge is at least 7 pixels from any edges on
		 * either its left or right line.
		 */
		
		// Check all edges associated with the left line
		h = 0;
		while(lines.get(newEdge.getLeft()).nextEdge(h) != null){
			h = lines.get(newEdge.getLeft()).nextEdge(h).getHeight();
			if(Math.abs(h - newEdge.getHeight()) < minEdgeSpacing){
				return false; // if the edge is invalid, skip the rest of the rule checking
			} else {
				isValid = true;
			}
		}
		
		// Check all edges associated with the right line
		h = 0;
		while(lines.get(newEdge.getRight()).nextEdge(h) != null){
			h = lines.get(newEdge.getRight()).nextEdge(h).getHeight();
			if(Math.abs(h - newEdge.getHeight()) < minEdgeSpacing){
				return false; // if the edge is invalid, skip the rest of the rule checking
			} else {
				isValid = true;
			}
		}
		
		/*
		 * RULE 3: Ensure the left point is actually left of the right point
		 */
		if(newEdge.getLeft() < newEdge.getRight()){
			isValid = true;
		} else {
			return false; // if the edge is invalid, skip the rest of the rule checking
		}
		
		/*
		 * RULE 4: Make sure that the edge is associated with neighboring lines only
		 */
		if(Math.abs(newEdge.getLeft() - newEdge.getRight()) > 1){
			return false; // if the edge is invalid, skip the rest of the rule checking
		} else {
			isValid = true;
		}

		//If all conditions were met, then return that the edge was valid
		if(isValid){
			return true;
		}
		return false; // safety net; should never actually get here
	}
	
	/**
	 * Method: Add edges to the lines
	 * @param newEdge - Edge to be added to the event.
	 * @return boolean: If edge is valid and is successfully added to its left and right lines, then return true, otherwise return false. 
	 */
	public boolean addEdge(Edge newEdge){
		//If the edge is valid, add it to both the associated left and right edges
		//Else remove the invalid edge instance and ensure that the edge has not been placed
		//  on either the left or the right line of the edge.
		if(isValidEdge(newEdge) && (lines.get(newEdge.getLeft()).addEdge(newEdge)) &&
				(lines.get(newEdge.getRight()).addEdge(newEdge))){
			return true;
		} else {
			lines.get(newEdge.getLeft()).removeEdge(newEdge);
			lines.get(newEdge.getRight()).removeEdge(newEdge);
		}
		return false;
	}
	
	/**
	 * Method: Sets the choice of the line
	 * @param position - Int, position of the line being set
	 * @param choice - String, the choice
	 * @return boolean: If true, then choice was added
	 */
	public boolean setChoice(int position, String choice){
		if(!isReady() && lines.get(position).setChoice(choice)){
			return true;
		}
		return false;
	}
	
	/**
	 * Method: Determines whether all n x m edges have been placed
	 * @return boolean: If true, then all edges have been placed for all rounds; false otherwise.
	 */
	public boolean isDone(){
		int finalCheck = numChoices*numRounds; // n x m
		int edgesPlaced = 0;
		int h;
		/* 
		 * Loop through each of the lines 0 through n-1. For each line, get each edge in turn and
		 * compare the position of the line (i) to the "left" position of each edge on the line. If
		 * the line position and the left position of the edge match, count the edge. Looking at only
		 * the left position of each edge prevents double-counting of edges.
		 * 
		 * NOTE: Only look at left lines of edges which leaves out the right-most line - i.e., numChoices-1 (n-1) vs. numChoices (n)
		 */
		for(int i = 0; i<(numChoices-1); i++){
			h = 0;
			while (lines.get(i).nextEdge(h) != null){
				if(i == lines.get(i).nextEdge(h).getLeft()){
					edgesPlaced++;
				}
				h = lines.get(i).nextEdge(h).getHeight();
			}
		}
		
		if (edgesPlaced == finalCheck){
			// The number of edges placed = N x M, so isDone() is true
			return true;
		} else if (edgesPlaced > finalCheck){
			// This should never happen, so throw an exception
			throw new Error("Number of edges placed (" + edgesPlaced + ") exceeds limit (" + finalCheck + ").");
		}
		
		return false;
	}
	
	/**
	 * Method: Determines whether all 'n' choices have been populated
	 * @return boolean: True if all choices have been populated; false otherwise
	 */
	public boolean isReady(){
		int j=0;
		
		//Check each lines choice
		for(int i=0; i < numChoices; i++){
			//If the lines choices is not null or empty string then increment J
			if(lines.get(i).getChoice() != null && lines.get(i).getChoice() != ""){
				j++;
			}
		}
		
		//If J is the same as the number of choices, then return that this is ready
		if(j==numChoices){
			return true;
		}
		return false;
	}
}
